#ifndef __ZKLOG__
#define __ZKLOG__

#include <sstream>
#include <string>
#include <memory>

#include "spdlog/common.h"
#include "spdlog/logger.h"
#include "spdlog/spdlog.h"
#include "spdlog/sinks/sink.h"
#include "spdlog/pattern_formatter.h"

namespace zk
{
    inline namespace log
    {
        namespace log_wrapper
        {
            using filename_t = spdlog::filename_t;
            using level_t = spdlog::level::level_enum;
            using sink = spdlog::sinks::sink;
            using sink_ptr = spdlog::sink_ptr;
            using logger = spdlog::logger;

            using spdlog::level::trace;
            using spdlog::level::debug;
            using spdlog::level::info;
            using spdlog::level::warn;
            using spdlog::level::err;
            using spdlog::level::critical;
            using spdlog::level::off;

            using spdlog::level::to_string_view;
            using spdlog::level::from_str;

            using spdlog::default_logger_raw;
            using spdlog::default_logger;

            // This function will automatically register logger, no need to call the function `register_logger`
            using spdlog::set_default_logger;

            // Get/Set global logging level
            using spdlog::get_level;
            using spdlog::set_level;

            using spdlog::flush_on;
            using spdlog::flush_every;

            // Loggers that have not called this function will not get it by `get`
            using spdlog::register_logger;
            using spdlog::get;

            using spdlog::drop;
            using spdlog::drop_all;
            using spdlog::shutdown;

            // import create_dir
            using spdlog::details::os::create_dir;
            using spdlog::details::os::dir_name;


            // make a colored console sink
            sink_ptr make_colored_console_sink(const std::string& pattern, level_t filter_level = info);
            // make a daily file sink which rotates on given time
            sink_ptr make_daily_file_sink(const filename_t& filepath, const std::string& pattern, level_t filter_level = info,
                uint16_t max_files = 30,
                int rotation_hour=23,
                int rotation_minute=59,
                bool truncate = false);

            typedef std::shared_ptr<logger> logger_ptr;
            logger_ptr make_multisink_logger(const std::string& logger_name, spdlog::sinks_init_list sinks,
                level_t filter_level = info);
            logger_ptr make_basic_file_logger(const std::string& logger_name,
                const filename_t& filepath,
                const std::string& pattern = "%8l | %Y-%m-%d %X.%f | %t | %s:%# | %v",
                level_t filter_level = info, bool truncate = false);
        } // namespace log_wrapper

        //////////////////////////////////////////////////////////////////////////
        // Compatible with streaming output
        class LogLine
        {
            std::ostringstream m_ss;
        public:
            LogLine() {}
            template <typename T> LogLine& operator<<(const T& t) { m_ss << t; return *this; }
            std::string str() const { return m_ss.str(); }
        };

        class LogStream {
            std::shared_ptr<spdlog::logger> m_log;
            spdlog::level::level_enum m_lvl;
            spdlog::source_loc m_loc;
        public:
            LogStream(std::shared_ptr<spdlog::logger> log, spdlog::level::level_enum lvl, spdlog::source_loc loc)
                : m_log{ log }, m_lvl{ lvl }, m_loc{ loc }{}
            bool operator==(const LogLine& line) {
                m_log->log(m_loc, m_lvl, "{}", line.str());
                return true;
            }
        };

    } // inline namespace log

} // namespace zk


//////////////////////////////////////////////////////////////////////////
// Compatible with streaming output
#define ZKLOG_STREAM(logger, level)                                                                      \
    logger && logger->should_log(level) &&                                                               \
        zk::LogStream(logger, level, spdlog::source_loc{__FILE__, __LINE__, SPDLOG_FUNCTION}) ==         \
        zk::LogLine()

#define ZKLOG_LOG(logger, level)            ZKLOG_STREAM((logger), (level))
#define ZKLOG_TRACE                         ZKLOG_LOG(spdlog::default_logger(), spdlog::level::trace)
#define ZKLOG_DEBUG                         ZKLOG_LOG(spdlog::default_logger(), spdlog::level::debug)
#define ZKLOG_INFO                          ZKLOG_LOG(spdlog::default_logger(), spdlog::level::info)
#define ZKLOG_WARNING                       ZKLOG_LOG(spdlog::default_logger(), spdlog::level::warn)
#define ZKLOG_ERROR                         ZKLOG_LOG(spdlog::default_logger(), spdlog::level::err)
#define ZKLOG_CRITICAL                      ZKLOG_LOG(spdlog::default_logger(), spdlog::level::critical)

//////////////////////////////////////////////////////////////////////////
// Use the fmt mode output of sdplog
#define ZKLOG_LOG_FMT(logger, level, ...)   SPDLOG_LOGGER_CALL((logger), (level), __VA_ARGS__)
#define ZKLOG_TRACE_FMT(...)                ZKLOG_LOG_FMT(spdlog::default_logger(), spdlog::level::trace, __VA_ARGS__)
#define ZKLOG_DEBUG_FMT(...)                ZKLOG_LOG_FMT(spdlog::default_logger(), spdlog::level::debug, __VA_ARGS__)
#define ZKLOG_INFO_FMT(...)                 ZKLOG_LOG_FMT(spdlog::default_logger(), spdlog::level::info, __VA_ARGS__)
#define ZKLOG_WARNING_FMT(...)              ZKLOG_LOG_FMT(spdlog::default_logger(), spdlog::level::warn, __VA_ARGS__)
#define ZKLOG_ERROR_FMT(...)                ZKLOG_LOG_FMT(spdlog::default_logger(), spdlog::level::err, __VA_ARGS__)
#define ZKLOG_CRITICAL_FMT(...)             ZKLOG_LOG_FMT(spdlog::default_logger(), spdlog::level::critical, __VA_ARGS__)


#endif // #ifndef __ZKLOG__
